/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);



bool SqlEngine::satisfy(const vector<SelCond>& cond,int key, std::string value)
{
        for(int i = 0 ; i < cond.size() ; ++i)
        {
                if(cond[i].attr==2) // comparing value
                {
                        int diff = strcmp(value.c_str(), cond[i].value);
                        switch (cond[i].comp)
                        {
                                case SelCond::EQ:
                                        if (diff != 0) return false; break;
                                case SelCond::NE:
                                        if (diff == 0) return false; break;
                                case SelCond::GT:
                                        if (diff <= 0) return false; break;
                                case SelCond::LT:
                                        if (diff >= 0) return false; break;
                                case SelCond::GE:
                                        if (diff < 0) return false; break;
                                case SelCond::LE:
                                        if (diff > 0) return false; break;
                        }
                }
                if(cond[i].attr==1 && cond[i].comp == SelCond::NE)
                        if(key==atoi(cond[i].value))
                                return false;
        }
        return true;
}

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select( int attr, const string& table, const vector< SelCond >& cond )
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  int eqValue, geValue, leValue;
  BTreeIndex bTree;
  IndexCursor cursor;

  vector<int> equalTo;
  vector<int> greaterEqual;
  vector<int> lessEqual;
  vector<int> notEqual;
  vector<int> condNonKey;

  bool needReadRecordFile = false;

  /*
   * if attr == 2, we need to SELECT value attribute;
   * if attr == 3, we need to SELECT key and value attributes;
   * in either condition, we need to read the actual RecordFile to get value information.
   */
  if( attr == 2 || attr == 3 )
      needReadRecordFile = true;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  // scan the table file from the beginning
  rid.pid = rid.sid = 0;
  count = 0;

  // see if the query has condition(s) on the key
  for( unsigned i = 0; i < cond.size(); i++ )
  {
      /*
       * if the condition involves the key,
       * store the corresponding value into the vector.
       */
      if( cond[i].attr == 1 )
      {
          switch( cond[i].comp )
          {
            case SelCond::EQ:
              equalTo.push_back( atoi( cond[i].value ) );
              break;
            case SelCond::GE:
              greaterEqual.push_back( atoi( cond[i].value) );
              break;
            case SelCond::GT:
              greaterEqual.push_back( atoi(cond[i].value) + 1 );
              break;
            case SelCond::LE:
              lessEqual.push_back( atoi(cond[i].value) );
              break;
            case SelCond::LT:
              lessEqual.push_back( atoi(cond[i].value) - 1 );
              break;
            case SelCond::NE:
              notEqual.push_back( atoi(cond[i].value) );
              break;
          }
      }

      /*
       * record down the condition index which does not involve in any key,
       * and needReadRecordFile will be set true
       */
      else
      {
          condNonKey.push_back( i );
          needReadRecordFile = true;
      }
  }

  // when there are more than one equal to conditions on the key
  if( equalTo.size() > 0 )
  {
      eqValue = equalTo[0];
      for( int i = 1; i < equalTo.size(); i++ )
          if( eqValue != equalTo[i] )
              goto print_count;
  }

  // get the maximum greaterEqual value in geValue
  if( greaterEqual.size() > 0 )
  {
      geValue = greaterEqual[0];
      for( int i = 1; i < greaterEqual.size(); i++ )
          if( greaterEqual[i] > geValue )
              geValue = greaterEqual[i];

      /*
       * if there was an equalTo condition,
       * and the equalTo value is less than the great than or equal to value
       * then under this condition, query should output nothing
       */
      if( equalTo.size() > 0 && eqValue < geValue )
          goto print_count;
  }

  // get the minimum lessEqual value in leValue
  if( lessEqual.size() > 0 )
  {
      leValue = lessEqual[0];
      for( int i = 1; i < lessEqual.size(); i++ )
          if( lessEqual[i] < leValue )
              leValue = lessEqual[i];

      /*
       * if there was an equalTo condition,
       * and the equalTo value is greater than the less than or equal to value
       * then under this condition, query should output nothing
       */
      if( equalTo.size() > 0 && eqValue > leValue )
          goto print_count;

      if( greaterEqual.size() > 0 && geValue > leValue )
          goto print_count;
  }

  /******************************************************************************
   *           The following is query part, searching using B+ tree             *
   ******************************************************************************/

  if( ( ( equalTo.size() > 0 || greaterEqual.size() > 0 || lessEqual.size() > 0 )
        || ( attr == 4 && condNonKey.size() == 0 ) )
        && ( bTree.open( table + ".idx", 'r' ) >= 0 ) )
  {
      // if there is a condition on key:
      if( equalTo.size() > 0 || greaterEqual.size() > 0 || lessEqual.size() > 0 )
      {

          if( equalTo.size() > 0 )
          {
              for( int i = 0; i < notEqual.size(); i++ )
                  if( eqValue == notEqual[i] )
                      goto print_count;

              // locate the eqValue key in the B+ tree
              if( ( rc = bTree.locate( eqValue, cursor ) ) < 0 )
              {
                fprintf(stderr, "Error: while locating a key in the B+ tree %s\n", table.c_str());
                goto exit_select;
              }

              // read the key and rid specified by the cursor
              // if the cursor is invalid, exit
              if( bTree.readForward( cursor, key, rid ) == RC_INVALID_CURSOR )
                  goto print_count;

              if( key != eqValue ) // if the key we are looking for does not exist
                  goto print_count;

              if( needReadRecordFile )
                  rf.read( rid, key, value );

              if( !SqlEngine::satisfy( cond, key, value ) )
              {
                  count = 0;
                  goto print_count;
              }
              else
              {
                  count = 1;
                  switch (attr)
                  {
                      case 1:  // SELECT key
                        fprintf(stdout, "%d\n", key);
                        break;
                      case 2:  // SELECT value
                        fprintf(stdout, "%s\n", value.c_str());
                        break;
                      case 3:  // SELECT *
                        fprintf(stdout, "%d '%s'\n", key, value.c_str());
                        break;
                  }
              }
          }


          // if there is no equal condition
          else
          {
              // if conditions give a lower bound
              if( greaterEqual.size() > 0 )
              {
                  // locate the geValue key in the B+ tree
                  if( ( rc = bTree.locate( geValue, cursor ) ) < 0 )
                  {
                    fprintf(stderr, "Error: while locating a key in the B+ tree %s\n", table.c_str());
                    goto exit_select;
                  }
              }
              else // doesnt have lower bound, set cursor to the first in leaf.
                  cursor = bTree.getLeastKeyCursor();

              // if the conditions give an upper bound
              if( lessEqual.size() > 0 )							//if have upper bound
              {
                // get one key and rid pair
                // if the cursor is invalid, exit select
                if( ( rc = bTree.readForward(cursor,key,rid) ) == RC_INVALID_CURSOR )
                    goto print_count;

                while(key <= leValue) //while key <= upper , check satisfy value constraint , count++
                {
                    if( needReadRecordFile )
                        rf.read(rid,key,value);

                    if(satisfy(cond,key,value)) //if satisfy value and NE constraint, then print.
                    {
                        count++;
                        switch (attr) {
                        case 1:  // SELECT key
                          fprintf(stdout, "%d\n", key);
                          break;
                        case 2:  // SELECT value
                          fprintf(stdout, "%s\n", value.c_str());
                          break;
                        case 3:  // SELECT *
                          fprintf(stdout, "%d '%s'\n", key, value.c_str());
                          break;
                        }
                    }
                    if( rc == RC_END_OF_TREE )
                        break;
                    // the cursor read is invalid, or get to the end of tree
                    if( ( rc = bTree.readForward( cursor, key, rid ) ) == RC_INVALID_CURSOR )
                        break;
                }
              }

              else // no upper bound,  until last of leaf, check satisfy value constraint , count++
              {
                while(true)
                {
                    if( ( rc = bTree.readForward( cursor, key, rid ) ) == RC_INVALID_CURSOR )
                        break;
                    if( needReadRecordFile )
                        rf.read(rid,key,value);

                    if(satisfy(cond,key,value))		//if satisfy value and NE constraint, then print.
                    {
                        count++;
                        switch (attr) {
                        case 1:  // SELECT key
                          fprintf(stdout, "%d\n", key);
                          break;
                        case 2:  // SELECT value
                          fprintf(stdout, "%s\n", value.c_str());
                          break;
                        case 3:  // SELECT *
                          fprintf(stdout, "%d '%s'\n", key, value.c_str());
                          break;
                        }
                    }

                    if( rc == RC_END_OF_TREE )
                        break;

                }
              }
          }
      }

      /*
       * When we have "SELECT COUNT(*)", and there is no conditions on value,
       * we don't need to read the record file for each tuple,
       * then we still use B+ tree,
       * with lower bound being the first key in all leaf nodes,
       * and no limit for the upper bound
       */
      else
      {
          // set cursor to the first in leaf.
          cursor = bTree.getLeastKeyCursor();
          do
          {
              bool valid = true;
              if( ( rc = bTree.readForward( cursor, key, rid ) ) == RC_INVALID_CURSOR )
                  break;
              for( int i = 0; i < notEqual.size(); i++ )
                  if( key == notEqual[i] )
                  {
                      valid = false;
                      break;
                  }

              if( valid )
                  count++;
          }while( rc != RC_END_OF_TREE );
      }
  }

  // when there aren't any condition on key, or there isn't a B+ tree index
  else
  {
      while (rid < rf.endRid()) {
        // read the tuple
        if ((rc = rf.read(rid, key, value)) < 0) {
          fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
          goto exit_select;
        }

        // check the conditions on the tuple
        for (unsigned i = 0; i < cond.size(); i++) {
          // compute the difference between the tuple value and the condition value
          switch (cond[i].attr) {
          case 1:
            diff = key - atoi(cond[i].value);
            break;
          case 2:
            diff = strcmp(value.c_str(), cond[i].value);
            break;
          }

          // skip the tuple if any condition is not met
          switch (cond[i].comp) {
          case SelCond::EQ:
            if (diff != 0) goto next_tuple;
            break;
          case SelCond::NE:
            if (diff == 0) goto next_tuple;
            break;
          case SelCond::GT:
            if (diff <= 0) goto next_tuple;
            break;
          case SelCond::LT:
            if (diff >= 0) goto next_tuple;
            break;
          case SelCond::GE:
            if (diff < 0) goto next_tuple;
            break;
          case SelCond::LE:
            if (diff > 0) goto next_tuple;
            break;
          }
        }

        // the condition is met for the tuple.
        // increase matching tuple counter
        count++;

        // print the tuple
        switch (attr) {
        case 1:  // SELECT key
          fprintf(stdout, "%d\n", key);
          break;
        case 2:  // SELECT value
          fprintf(stdout, "%s\n", value.c_str());
          break;
        case 3:  // SELECT *
          fprintf(stdout, "%d '%s'\n", key, value.c_str());
          break;
        }

        // move to the next tuple
        next_tuple:
        ++rid;
      }
  }

  print_count:
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
  exit_select:
  rf.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  RecordFile rf;  // RecordFile containing the table
  RecordId rid;

  RC rc;
  int key;
  string value, line;
  ifstream inFile;
  BTreeIndex bTree;


  // open the table file
  if( ( rc = rf.open( table+".tbl", 'w' ) ) < 0 )
  {
    fprintf( stderr, "Error: error in opening table %s \n", table.c_str() );
    return rc;
  }

  // open the loadfile
  inFile.open( loadfile.c_str() );
  if(inFile.fail())
  {
    inFile.clear();
    inFile.close();
    fprintf( stderr, "Error: file %s does not exist\n", loadfile.c_str() );
    return RC_FILE_OPEN_FAILED;
  }

  // if index is true, create a B+ tree index for the table
  if( index )
  {
      if( ( rc = bTree.open( table + ".idx", 'w' ) ) < 0 )
          return rc;
  }


  // read the loadfile line by line, 
  // parse a line, and append the line to the record file
  while( getline( inFile, line ) )
  {
    if ( ( rc = parseLoadLine(line, key, value) ) < 0 )
    {
      fprintf( stderr, "Error: unable to parse this line\n" );
      exit(1);
    }

    if ( ( rc = rf.append( key, value, rid ) ) < 0 )
    {
      fprintf( stderr, "Error: unable to append the key value pair\n" );
      exit(1);
    }

    if( index )
    {
        if( ( rc = bTree.insert( key, rid ) ) < 0 )
            return rc;
    }

  }

  /* your code here */

  if( index )
      bTree.close();

  inFile.close();
  rf.close();
  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}

